﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IWMAG.Scripting.Helpers;
using IWMAG.Scripting.AST;
using System.Reflection;
using IWMAG.Scripting.Interpreter;
using IWMAG.Scripting.AST.Declarations;
using IWMAG.Scripting.Compiler;
using System.Collections;

namespace IWMAG.Scripting.Types {
    public static class Marshaller {
        public static CodeType Marshal(Type type, CompileTable sym) {
            if (type == typeof(string)) {
                return CodeType.String;
            } else if (type == typeof(void)) {
                return CodeType.Void;
            } else if (type == typeof(int) || type == typeof(long)) {
                return CodeType.Int;
            } else if (type == typeof(float) || type == typeof(double)) {
                return CodeType.Real;
            } else if (type == typeof(bool)) {
                return CodeType.Bool;
            } else if (type.IsArray) {
                return new ArrayType(Marshal(type.GetElementType(), sym));
            } else if (typeof(IEnumerable).IsAssignableFrom(type)) {
                return new ArrayType(Marshal(type.GetGenericArguments()[0], sym));
            } else {
                return sym.Dealias(new ReferenceType(type.FullName.Split('.')));
            }
        }

        public static Type Marshal(CodeType type, CompileTable sym) {
            type = sym.Dealias(type);
            if (type is PrimitiveType) {
                switch ((type as PrimitiveType).Type) {
                    case PrimitiveType.Primitive.Bool:
                        return typeof(bool);
                    case PrimitiveType.Primitive.Int:
                        return typeof(int);
                    case PrimitiveType.Primitive.Real:
                        return typeof(float);
                    case PrimitiveType.Primitive.String:
                        return typeof(string);
                    case PrimitiveType.Primitive.Void:
                        return typeof(void);
                }
            } else if (type is ArrayType) {
                return Marshal((type as ArrayType).BaseType, sym).MakeArrayType();
            } else if (type is ReferenceType) {
                string qualified = string.Join(".", (type as ReferenceType).Identifier);
                foreach (Assembly asm in sym.Engine.ExposedAssemblies) {
                    try {
                        return asm.GetType(qualified, true);
                    } catch { }
                }
            }

            return typeof(void);
        }

        public static Signature GetSignature(MethodBase method, CompileTable sym) {
            Signature s = new Signature();
            s.Name = method.Name;
            s.IsExtern = true;
            s.ReturnType = Marshaller.Marshal(method is ConstructorInfo 
                ? (method as ConstructorInfo).DeclaringType 
                : (method as MethodInfo).ReturnType, sym);

            ParameterInfo[] param = method.GetParameters();
            s.Parameters = new VariableDeclaration[param.Length];

            int i = 0;
            Lexeme l = default(Lexeme);
            foreach (ParameterInfo p in method.GetParameters()) {
                s.Parameters[i++] = new VariableDeclaration(l, Marshaller.Marshal(p.ParameterType, sym), p.Name);
            }

            return s;
        }

        public static object Cast(object o, Type to, CompileTable sym) {
            return Cast(o, Marshaller.Marshal(o.GetType(), sym), to);
        }

        public static object Cast(object o, CodeType from, Type to) {
            if (to == typeof(bool)) {
                if (from.Equals(CodeType.Bool)) {
                    return o;
                } else if (from.Equals(CodeType.Int)) {
                    return (int) o != 0;
                } else if (from.Equals(CodeType.Real)) {
                    return (double) o != 0.0;
                } else if (from.Equals(CodeType.String)) {
                    return (string) o == "True" || (string) o == "1";
                } else {
                }
            } else if (to == typeof(int)) {
                if (from.Equals(CodeType.Bool)) {
                    return (bool)o ? 1.0 : 0.0;
                } else if (from.Equals(CodeType.Int)) {
                    return o;
                } else if (from.Equals(CodeType.Real)) {
                    return Convert.ChangeType(o, typeof(int));
                } else if (from.Equals(CodeType.String)) {
                    return int.Parse(o as string);
                } else {
                }
            } else if (to == typeof(double)) {
                if (from.Equals(CodeType.Bool)) {
                    return (bool)o ? 1 : 0;
                } else if (from.Equals(CodeType.Int)) {
                    return (int)o + 0.0;
                } else if (from.Equals(CodeType.Real)) {
                    return o;
                } else if (from.Equals(CodeType.String)) {
                    return double.Parse(o as string);
                } else {
                }
            } else if (to == typeof(string)) {
                return o.ToString();
            }

            try {
                return typeof(Marshaller).GetMethod("RuntimeCast").MakeGenericMethod(to).Invoke(null,
                        new object[] { o }
                    );
            } catch {
                return o;
            }
        }

        // dynamic casting
        public static T RuntimeCast<T>(object o) {
            return (T)o;
        }
    }
}
